"""钉钉开放平台API适配器"""

import time
import hmac
import hashlib
import base64
import urllib.parse
from typing import Dict, Any, List, Optional, Union

from cn_api_adapters.common.base import BaseAPI
from cn_api_adapters.common.utils import generate_timestamp, generate_nonce, hmac_sign, safe_json_dumps
from cn_api_adapters.common.alibabacloud.exceptions import (
    DingTalkError, DingTalkAuthError, DingTalkAccessTokenError, 
    DingTalkRateLimitError, DingTalkUserNotFoundError,
    DingTalkDepartmentNotFoundError, DingTalkMessageError,
    DingTalkMediaError, DingTalkRobotError, DingTalkRobotAuthError
)


class DingTalkAPI(BaseAPI):
    """
    钉钉开放平台API适配器
    
    提供钉钉开放平台各类API的调用功能，包括：
    - 身份认证
    - 用户管理
    - 部门管理
    - 消息发送
    - 媒体管理
    - 审批管理
    - 机器人管理
    """
    
    BASE_URL = "https://oapi.dingtalk.com"
    
    def __init__(self, 
                 app_key: Optional[str] = None,
                 app_secret: Optional[str] = None,
                 access_token: Optional[str] = None,
                 timeout: int = 30,
                 max_retries: int = 3,
                 retry_delay: float = 1.0,
                 debug: bool = False):
        """
        初始化钉钉API
        
        Args:
            app_key: 应用的AppKey
            app_secret: 应用的AppSecret
            access_token: 直接提供access_token（可选）
            timeout: 请求超时时间（秒）
            max_retries: 最大重试次数
            retry_delay: 重试间隔（秒）
            debug: 是否开启调试模式
        """
        super().__init__(timeout=timeout, 
                         max_retries=max_retries, 
                         retry_delay=retry_delay,
                         debug=debug)
        
        self.app_key = app_key
        self.app_secret = app_secret
        self._access_token = access_token
        self._token_expires_at = 0
        self._token_lock = False
    
    @property
    def access_token(self) -> str:
        """
        获取AccessToken（自动刷新）
        
        Returns:
            AccessToken字符串
            
        Raises:
            DingTalkAuthError: 认证失败
        """
        # 检查是否需要刷新token
        if not self._access_token or time.time() > self._token_expires_at - 60:
            self._refresh_access_token()
        return self._access_token
    
    def _refresh_access_token(self):
        """
        刷新AccessToken
        
        Raises:
            DingTalkAuthError: 认证失败
        """
        # 防止并发刷新token
        if hasattr(self, '_token_lock') and self._token_lock:
            # 等待其他线程完成token刷新
            import threading
            for _ in range(10):
                if not self._token_lock:
                    return
                time.sleep(0.1)
        
        self._token_lock = True
        try:
            # 再次检查token是否需要刷新（可能被其他线程刷新）
            if self._access_token and time.time() <= self._token_expires_at - 60:
                return
            
            if not self.app_key or not self.app_secret:
                raise DingTalkAuthError("缺少AppKey或AppSecret")
            
            url = f"{self.BASE_URL}/gettoken"
            params = {
                "appkey": self.app_key,
                "appsecret": self.app_secret
            }
            
            response = self.get(url, params=params)
            
            if not response.get("errcode") == 0:
                error_code = str(response.get("errcode", "unknown"))
                error_msg = response.get("errmsg", "获取AccessToken失败")
                if error_code in ["40001", "40003"]:
                    raise DingTalkAccessTokenError(error_msg, error_code, response)
                else:
                    raise DingTalkAuthError(error_msg, error_code, response)
            
            self._access_token = response["access_token"]
            expires_in = response.get("expires_in", 7200)
            self._token_expires_at = time.time() + expires_in
        finally:
            self._token_lock = False
    
    def _handle_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
        """
        处理API响应
        
        Args:
            response: API响应数据
            
        Returns:
            处理后的响应数据
            
        Raises:
            DingTalkError: API调用失败
        """
        # 钉钉API错误格式：{"errcode": 错误码, "errmsg": "错误消息"}
        if isinstance(response, dict) and "errcode" in response:
            errcode = response["errcode"]
            errmsg = response.get("errmsg", "未知错误")
            
            if errcode != 0:
                # 处理特定错误码
                error_code = str(errcode)
                
                # 认证相关错误
                if error_code in ["40001", "40002", "40003", "40004"]:
                    # 清除token，下次请求会自动刷新
                    self._access_token = None
                    raise DingTalkAccessTokenError(errmsg, error_code, response)
                
                # 限流错误
                elif error_code in ["429", "408"]:
                    retry_after = response.get("retry_after", 5)
                    raise DingTalkRateLimitError(errmsg, error_code, response, retry_after)
                
                # 用户不存在
                elif error_code == "60111":
                    raise DingTalkUserNotFoundError(errmsg, error_code, response)
                
                # 部门不存在
                elif error_code == "60002":
                    raise DingTalkDepartmentNotFoundError(errmsg, error_code, response)
                
                # 消息发送失败
                elif error_code.startswith("30"):
                    raise DingTalkMessageError(errmsg, error_code, response)
                
                # 媒体相关错误
                elif error_code.startswith("40") and "media" in errmsg.lower():
                    raise DingTalkMediaError(errmsg, error_code, response)
                
                # 其他错误
                else:
                    raise DingTalkError(errmsg, error_code, response)
        
        return response
    
    # ========== 用户管理API ==========
    
    def get_user_info_by_code(self, code: str) -> Dict[str, Any]:
        """
        通过免登授权码获取用户信息
        
        Args:
            code: 免登授权码
            
        Returns:
            用户信息
        """
        url = f"{self.BASE_URL}/user/getuserinfo"
        params = {
            "access_token": self.access_token,
            "code": code
        }
        return self.get(url, params=params)
    
    def get_user_detail(self, userid: str) -> Dict[str, Any]:
        """
        获取用户详情
        
        Args:
            userid: 用户在企业内的唯一标识
            
        Returns:
            用户详情
        """
        url = f"{self.BASE_URL}/user/get"
        params = {
            "access_token": self.access_token,
            "userid": userid
        }
        return self.get(url, params=params)
    
    def get_dept_users(self, dept_id: int, 
                      fetch_child: bool = False,
                      status_list: str = "0",
                      cursor: int = 0,
                      size: int = 100) -> Dict[str, Any]:
        """
        获取部门用户列表
        
        Args:
            dept_id: 部门ID
            fetch_child: 是否递归获取子部门用户
            status_list: 员工状态列表，0-未激活，1-已激活，2-已禁用
            cursor: 分页游标
            size: 分页大小
            
        Returns:
            用户列表
        """
        url = f"{self.BASE_URL}/user/listbypage"
        params = {
            "access_token": self.access_token,
            "department_id": dept_id,
            "fetch_child": fetch_child,
            "status_list": status_list,
            "cursor": cursor,
            "size": size
        }
        return self.get(url, params=params)
    
    def create_user(self, user_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        创建用户
        
        Args:
            user_data: 用户信息
            
        Returns:
            创建结果
        """
        url = f"{self.BASE_URL}/user/create"
        params = {"access_token": self.access_token}
        return self.post(url, params=params, json=user_data)
    
    def update_user(self, userid: str, user_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        更新用户信息
        
        Args:
            userid: 用户ID
            user_data: 更新的用户信息
            
        Returns:
            更新结果
        """
        url = f"{self.BASE_URL}/user/update"
        params = {"access_token": self.access_token}
        user_data["userid"] = userid
        return self.post(url, params=params, json=user_data)
    
    def delete_user(self, userid: str) -> Dict[str, Any]:
        """
        删除用户
        
        Args:
            userid: 用户ID
            
        Returns:
            删除结果
        """
        url = f"{self.BASE_URL}/user/delete"
        params = {
            "access_token": self.access_token,
            "userid": userid
        }
        return self.get(url, params=params)
    
    # ========== 部门管理API ==========
    
    def get_department_list(self, id: Optional[int] = None) -> Dict[str, Any]:
        """
        获取部门列表
        
        Args:
            id: 部门ID，不指定则获取全部部门
            
        Returns:
            部门列表
        """
        url = f"{self.BASE_URL}/department/list"
        params = {"access_token": self.access_token}
        if id is not None:
            params["id"] = id
        return self.get(url, params=params)
    
    def get_department_detail(self, id: int) -> Dict[str, Any]:
        """
        获取部门详情
        
        Args:
            id: 部门ID
            
        Returns:
            部门详情
        """
        url = f"{self.BASE_URL}/department/get"
        params = {
            "access_token": self.access_token,
            "id": id
        }
        return self.get(url, params=params)
    
    def create_department(self, dept_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        创建部门
        
        Args:
            dept_data: 部门信息
            
        Returns:
            创建结果
        """
        url = f"{self.BASE_URL}/department/create"
        params = {"access_token": self.access_token}
        return self.post(url, params=params, json=dept_data)
    
    def update_department(self, id: int, dept_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        更新部门信息
        
        Args:
            id: 部门ID
            dept_data: 更新的部门信息
            
        Returns:
            更新结果
        """
        url = f"{self.BASE_URL}/department/update"
        params = {"access_token": self.access_token}
        dept_data["id"] = id
        return self.post(url, params=params, json=dept_data)
    
    def delete_department(self, id: int) -> Dict[str, Any]:
        """
        删除部门
        
        Args:
            id: 部门ID
            
        Returns:
            删除结果
        """
        url = f"{self.BASE_URL}/department/delete"
        params = {
            "access_token": self.access_token,
            "id": id
        }
        return self.get(url, params=params)
    
    # ========== 消息发送API ==========
    
    def send_work_notify(self, agent_id: int, 
                        msg: Dict[str, Any], 
                        to_users: Optional[List[str]] = None,
                        to_depts: Optional[List[int]] = None,
                        to_all_user: bool = False) -> Dict[str, Any]:
        """
        发送工作通知
        
        Args:
            agent_id: 应用ID
            msg: 消息内容
            to_users: 接收人用户ID列表
            to_depts: 接收人部门ID列表
            to_all_user: 是否发送给全员
            
        Returns:
            发送结果
        """
        url = f"{self.BASE_URL}/topapi/message/corpconversation/asyncsend_v2"
        params = {"access_token": self.access_token}
        
        data = {
            "agent_id": agent_id,
            "msg": msg,
            "to_all_user": to_all_user
        }
        
        if to_users:
            data["userid_list"] = ",".join(to_users)
        if to_depts:
            data["dept_id_list"] = ",".join(map(str, to_depts))
        
        return self.post(url, params=params, json=data)
    
    def send_text_message(self, agent_id: int, 
                         content: str,
                         to_users: Optional[List[str]] = None,
                         to_depts: Optional[List[int]] = None,
                         to_all_user: bool = False) -> Dict[str, Any]:
        """
        发送文本消息
        
        Args:
            agent_id: 应用ID
            content: 消息内容
            to_users: 接收人用户ID列表
            to_depts: 接收人部门ID列表
            to_all_user: 是否发送给全员
            
        Returns:
            发送结果
        """
        msg = {
            "msgtype": "text",
            "text": {"content": content}
        }
        return self.send_work_notify(agent_id, msg, to_users, to_depts, to_all_user)
    
    def send_markdown_message(self, agent_id: int,
                             title: str,
                             text: str,
                             to_users: Optional[List[str]] = None,
                             to_depts: Optional[List[int]] = None,
                             to_all_user: bool = False) -> Dict[str, Any]:
        """
        发送Markdown消息
        
        Args:
            agent_id: 应用ID
            title: 消息标题
            text: Markdown格式的消息内容
            to_users: 接收人用户ID列表
            to_depts: 接收人部门ID列表
            to_all_user: 是否发送给全员
            
        Returns:
            发送结果
        """
        msg = {
            "msgtype": "markdown",
            "markdown": {
                "title": title,
                "text": text
            }
        }
        return self.send_work_notify(agent_id, msg, to_users, to_depts, to_all_user)
    
    # ========== 媒体管理API ==========
    
    def upload_media(self, media_type: str, 
                    file_path: str,
                    filename: Optional[str] = None) -> Dict[str, Any]:
        """
        上传媒体文件
        
        Args:
            media_type: 媒体类型（image, voice, file）
            file_path: 文件路径
            filename: 文件名（可选）
            
        Returns:
            上传结果
        """
        url = f"{self.BASE_URL}/media/upload"
        params = {
            "access_token": self.access_token,
            "type": media_type
        }
        
        import os
        if not filename:
            filename = os.path.basename(file_path)
        
        with open(file_path, 'rb') as f:
            files = {'media': (filename, f)}
            return self.post(url, params=params, files=files)
    
    def download_media(self, media_id: str, save_path: str) -> bool:
        """
        下载媒体文件
        
        Args:
            media_id: 媒体文件ID
            save_path: 保存路径
            
        Returns:
            是否下载成功
        """
        url = f"{self.BASE_URL}/media/get"
        params = {
            "access_token": self.access_token,
            "media_id": media_id
        }
        
        response = self._session.get(url, params=params, stream=True)
        response.raise_for_status()
        
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        
        return True
    
    # ========== 机器人消息 ==========
    
    def send_robot_message(self, webhook: str,
                          secret: Optional[str] = None,
                          msg: Dict[str, Any]) -> Dict[str, Any]:
        """
        发送机器人消息
        
        Args:
            webhook: 机器人webhook地址
            secret: 机器人密钥（签名验证用）
            msg: 消息内容
            
        Returns:
            发送结果
        """
        # 处理签名
        headers = {"Content-Type": "application/json"}
        if secret:
            timestamp = str(int(time.time() * 1000))
            sign = self._generate_robot_sign(timestamp, secret)
            if "?" in webhook:
                webhook = f"{webhook}&timestamp={timestamp}&sign={sign}"
            else:
                webhook = f"{webhook}?timestamp={timestamp}&sign={sign}"
        
        response = self.post(webhook, json=msg, headers=headers)
        
        # 机器人错误处理
        if isinstance(response, dict) and "errcode" in response:
            if response["errcode"] != 0:
                error_code = str(response["errcode"])
                error_msg = response.get("errmsg", "机器人消息发送失败")
                raise DingTalkRobotError(error_msg, error_code, response)
        
        return response
    
    def send_robot_text(self, webhook: str,
                       secret: Optional[str] = None,
                       content: str,
                       at_mobiles: Optional[List[str]] = None,
                       at_user_ids: Optional[List[str]] = None,
                       is_at_all: bool = False) -> Dict[str, Any]:
        """
        发送机器人文本消息
        
        Args:
            webhook: 机器人webhook地址
            secret: 机器人密钥
            content: 消息内容
            at_mobiles: @的手机号列表
            at_user_ids: @的用户ID列表
            is_at_all: 是否@所有人
            
        Returns:
            发送结果
        """
        msg = {
            "msgtype": "text",
            "text": {"content": content},
            "at": {
                "atMobiles": at_mobiles or [],
                "atUserIds": at_user_ids or [],
                "isAtAll": is_at_all
            }
        }
        return self.send_robot_message(webhook, secret, msg)
    
    def send_robot_markdown(self, webhook: str,
                           secret: Optional[str] = None,
                           title: str,
                           text: str) -> Dict[str, Any]:
        """
        发送机器人Markdown消息
        
        Args:
            webhook: 机器人webhook地址
            secret: 机器人密钥
            title: 消息标题
            text: Markdown格式的消息内容
            
        Returns:
            发送结果
        """
        msg = {
            "msgtype": "markdown",
            "markdown": {
                "title": title,
                "text": text
            }
        }
        return self.send_robot_message(webhook, secret, msg)
    
    def _generate_robot_sign(self, timestamp: str, secret: str) -> str:
        """
        生成机器人签名
        
        Args:
            timestamp: 时间戳
            secret: 密钥
            
        Returns:
            签名结果
        """
        string_to_sign = f"{timestamp}\n{secret}"
        hmac_code = hmac.new(string_to_sign.encode('utf-8'), 
                           digestmod=hashlib.sha256).digest()
        return urllib.parse.quote(base64.b64encode(hmac_code))


class DingTalkRobot(DingTalkAPI):
    """
    钉钉机器人专用API类
    简化机器人消息发送的使用方式
    """
    
    def __init__(self, webhook: str,
                 secret: Optional[str] = None,
                 timeout: int = 30):
        """
        初始化钉钉机器人
        
        Args:
            webhook: 机器人webhook地址
            secret: 机器人密钥
            timeout: 请求超时时间
        """
        super().__init__(timeout=timeout)
        self.webhook = webhook
        self.secret = secret
    
    def send(self, msg: Dict[str, Any]) -> Dict[str, Any]:
        """
        发送消息
        
        Args:
            msg: 消息内容
            
        Returns:
            发送结果
        """
        return self.send_robot_message(self.webhook, self.secret, msg)
    
    def send_text(self, content: str,
                 at_mobiles: Optional[List[str]] = None,
                 at_user_ids: Optional[List[str]] = None,
                 is_at_all: bool = False) -> Dict[str, Any]:
        """
        发送文本消息
        
        Args:
            content: 消息内容
            at_mobiles: @的手机号列表
            at_user_ids: @的用户ID列表
            is_at_all: 是否@所有人
            
        Returns:
            发送结果
        """
        return self.send_robot_text(self.webhook, self.secret, 
                                  content, at_mobiles, at_user_ids, is_at_all)
    
    def send_markdown(self, title: str, text: str) -> Dict[str, Any]:
        """
        发送Markdown消息
        
        Args:
            title: 消息标题
            text: Markdown格式的消息内容
            
        Returns:
            发送结果
        """
        return self.send_robot_markdown(self.webhook, self.secret, title, text)